package com.mldong.flow.engine.core;

import cn.hutool.core.lang.Dict;
import cn.hutool.json.JSONUtil;
import com.mldong.flow.engine.FlowEngine;
import com.mldong.flow.engine.cfg.Configuration;
import com.mldong.flow.engine.entity.ProcessDefine;
import com.mldong.flow.engine.entity.ProcessInstance;
import com.mldong.flow.engine.entity.ProcessTask;
import com.mldong.flow.engine.enums.ErrEnum;
import com.mldong.flow.engine.enums.ProcessInstanceStateEnum;
import com.mldong.flow.engine.enums.TaskStateEnum;
import com.mldong.flow.engine.ex.JFlowException;
import com.mldong.flow.engine.mapper.ProcessDefineMapper;
import com.mldong.flow.engine.mapper.ProcessInstanceMapper;
import com.mldong.flow.engine.mapper.ProcessTaskMapper;
import com.mldong.flow.engine.model.NodeModel;
import com.mldong.flow.engine.model.ProcessModel;
import com.mldong.flow.engine.parser.ModelParser;
import com.mldong.flow.engine.service.ProcessDefineService;
import com.mldong.flow.engine.service.ProcessInstanceService;
import com.mldong.flow.engine.service.ProcessTaskService;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;

import java.util.Date;
import java.util.List;

/**
 *
 * 工作流引擎实现
 * @author mldong
 * @date 2023/5/29
 */
public class FlowEngineImpl implements FlowEngine {
    private SqlSessionFactory sqlSessionFactory;
    protected Configuration configuration;
    private ProcessDefineService processDefineService;
    private ProcessInstanceService processInstanceService;
    private ProcessTaskService processTaskService;
    @Override
    public FlowEngine configure(Configuration config) {
        this.configuration = config;
        sqlSessionFactory = ServiceContext.find(SqlSessionFactory.class);
        processDefineService = ServiceContext.find(ProcessDefineService.class);
        processInstanceService = ServiceContext.find(ProcessInstanceService.class);
        processTaskService = ServiceContext.find(ProcessTaskService.class);
        return this;
    }

    @Override
    public ProcessDefineService processDefineService() {
        return processDefineService;
    }

    @Override
    public ProcessInstanceService processInstanceService() {
        return processInstanceService;
    }

    @Override
    public ProcessTaskService processTaskService() {
        return processTaskService;
    }

    @Override
    public ProcessInstance startProcessInstanceById(Long id, String operator, Dict args) {
        // 1. 根据流程定义ID查询流程定义文件
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        ProcessDefineMapper processDefineMapper = sqlSession.getMapper(ProcessDefineMapper.class);
        ProcessDefine processDefine = processDefineMapper.selectById(id);
        if(processDefine==null) {
            throw new JFlowException(ErrEnum.NOT_FOUND_PROCESS_DEFINE);
        }
        Date now = new Date();
        // 2. 将流程定义文件转成流程模型
        ProcessModel processModel = ModelParser.parse(processDefine.getContent());
        // 3. 根据流程定义信息创建流程实例
        ProcessInstance processInstance = new ProcessInstance();
        processInstance.setProcessDefineId(processDefine.getId());
        processInstance.setOperator(operator);
        processInstance.setProcessDefineId(processDefine.getId());
        processInstance.setState(ProcessInstanceStateEnum.DOING.getCode());
        processInstance.setCreateTime(now);
        processInstance.setUpdateTime(now);
        processInstance.setVariable(JSONUtil.toJsonStr(args));
        ProcessInstanceMapper processInstanceMapper = sqlSession.getMapper(ProcessInstanceMapper.class);
        // 插入流程实例表
        processInstanceMapper.insert(processInstance);
        // 4. 构建执行参数对象
        Execution execution = new Execution();
        execution.setProcessModel(processModel);
        execution.setProcessInstance(processInstance);
        execution.setProcessInstanceId(processInstance.getId());
        execution.setEngine(this);
        execution.setArgs(args);
        // 5. 拿到开始节点模型，调用其execute方法
        processModel.getStart().execute(execution);
        // 关闭会话-这里先简单使用，后面再使用Spring的全局事务处理器
        sqlSession.close();
        return processInstance;
    }

    @Override
    public List<ProcessTask> executeProcessTask(Long processTaskId, String operator, Dict args) {
        // 1. 根据id查询正在进行中的流程任务
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        ProcessTaskMapper processTaskMapper = sqlSession.getMapper(ProcessTaskMapper.class);
        ProcessTask processTask = processTaskMapper.selectById(processTaskId);
        if(processTask == null || !TaskStateEnum.DOING.getCode().equals(processTask.getTaskState())) {
            throw new JFlowException(ErrEnum.NOT_FOUND_DOING_PROCESS_TASK);
        }
        // 2. 根据流程任务查询流程实例
        ProcessInstanceMapper processInstanceMapper = sqlSession.getMapper(ProcessInstanceMapper.class);
        ProcessInstance processInstance = processInstanceMapper.selectById(processTask.getProcessInstanceId());
        // 3. 根据流程实例查询流程定义
        ProcessDefineMapper processDefineMapper = sqlSession.getMapper(ProcessDefineMapper.class);
        ProcessDefine processDefine = processDefineMapper.selectById(processInstance.getProcessDefineId());
        // 4. 将流程定义文件转成流程模型
        ProcessModel processModel = ModelParser.parse(processDefine.getContent());
        // 5. 将流程任务状态修改为已完成
        processTask.setTaskState(TaskStateEnum.FINISHED.getCode());
        processTask.setUpdateTime(new Date());
        processTaskMapper.updateById(processTask);
        // 6. 根据流程定义、实例、任务构建执行参数对象
        Execution execution = new Execution();
        execution.setProcessModel(processModel);
        execution.setProcessInstance(processInstance);
        execution.setProcessInstanceId(processInstance.getId());
        execution.setEngine(this);
        Dict processInstanceVariable = JSONUtil.toBean(processInstance.getVariable(),Dict.class);
        Dict newArgs = Dict.create();
        newArgs.putAll(processInstanceVariable);
        newArgs.putAll(args);
        execution.setArgs(newArgs);
        // 7. 根据流程任务名称获取对应的任务节点模型
        NodeModel nodeModel = processModel.getNode(processTask.getTaskName());
        nodeModel.execute(execution);
        sqlSession.close();
        // 8. 调用节点模型执行方法
        return execution.getProcessTaskList();
    }



}
